package glemipsc;
import glemipsc.implementations.Argument;
import glemipsc.implementations.Implementation;
import glemipsc.instructions.Expression;
import glemipsc.instructions.Instruction;
import glemipsc.instructions.Return;
import glemipsc.instructions.VariableRef;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.*;

public class Function extends SymbolElement implements SymbolTable, Serializable {
	private HashMap<String,SymbolElement> symbols = new HashMap<String,SymbolElement>();
	private ArrayList<String> parameterNames = new ArrayList<String>();
	private ArrayList<Instruction> instructions = new ArrayList<Instruction>();
	private boolean coded = false;
	private ObjectFile objectFile = null;
	private ArrayList<glemipsc.implementations.Instruction> compiledInstructions = new ArrayList<glemipsc.implementations.Instruction>();
	private boolean computed = false;

	public Function(String name) {
		super(name);
	}
	
	public Function(SymbolElement el) {
		super(el.name);
		this.type = el.type;
	}
	
	public void replace(Function f) {
		f.symbols = symbols;
		f.parameterNames = parameterNames;
		f.instructions = instructions;
		f.coded = coded;
		f.objectFile = objectFile;
		f.addr.setSegment(addr.getSegment());
		f.addr.setValue(addr.getValue());
		this.useStoreAddress(f);
	}
	
	public void addSymbol(SymbolElement e) {
		if (exists(e.getName()))
			return;
		symbols.put(e.getName(), e);
	}
	
	public void addSymbols(List es) {
		if (es == null)
			return;
		Iterator iter = es.iterator();
		while (iter.hasNext()) {
			SymbolElement el = (SymbolElement) iter.next();
			addSymbol(el);
		}
	}
	
	public boolean exists(String name) {
		return symbols.get(name) != null;
	}
	
	public SymbolElement get(String name) {
		return symbols.get(name);
	}
	
	public List<SymbolElement> getStaticVariables() {
		ArrayList<SymbolElement> list = new ArrayList<SymbolElement>();
		
		Iterator<SymbolElement> siter = symbols.values().iterator();
		while (siter.hasNext()) {
			SymbolElement el = siter.next();
			if (!el.getType().isStatic())
				continue;
			list.add(el);
		}
		return list;
	}

	public void setCoded(boolean coded) {
		this.coded = coded;
	}
	public boolean isCoded() {
		return coded;
	}
	
	public void setInstructions(ArrayList instr) {
		instructions = instr;
	}
	
	public void setObjectFile(ObjectFile objectFile) {
		this.objectFile = objectFile;
	}
	public ObjectFile getObjectFile() {
		return objectFile;
	}

	public void setParametersList(List parameters) {
		Iterator iter = parameters.iterator();
		Implementation impl = GLEMipsC.compiler.getEnvironment().implementation;
		Address addr = new RelativeAddress(impl.getStackPointerRegister(), 0);
		while (iter.hasNext()) {
			SymbolElement el = (SymbolElement) iter.next();
			el.setStoreAddress(addr);
			addSymbol(el);
			parameterNames.add(el.getName());
			addr = new RelativeAddress(impl.getStackPointerRegister(), addr.getValue() + el.getStoredSize());
		}
	}
	
	public boolean equals(Object o) {
		Function f = (Function) o;
		if (!f.name.equals(this.name))
			return false;
		if (!this.type.equals(f.type))
			return false;
		if (this.parameterNames.size() != f.parameterNames.size())
			return false;
		Iterator<String> iter = parameterNames.iterator();
		while (iter.hasNext()) {
			SymbolElement myparam = symbols.get(iter.next());
			if (!f.parameterNames.contains(myparam.getName()))
				return false;
			SymbolElement hisparam = f.symbols.get(myparam.getName());
			if (hisparam == null)
				return false;
			if (!myparam.getType().equals(hisparam.getType()))
				return false;
		}
		return true;
	}
	
	public boolean isStored() {
		return coded;
	}
	
	public void setStoreAddress(Address addr) {
		super.setStoreAddress(addr);
		
		int address = addr.getValue();
		Iterator<glemipsc.implementations.Instruction> iter = compiledInstructions.iterator();
		while (iter.hasNext()) {
			glemipsc.implementations.Instruction instr = iter.next();
			instr.setAddress(address);
			//address += instr.getSize();
			if (!instr.isNop())
				address += 1;
		}
	}
	
	public List<String> getParameterNames() {
		return this.parameterNames;
	}
	
	public void computeOpCode(Linker linker) {
		if (computed)
			return;
		computed = true;
		Implementation impl = GLEMipsC.compiler.environment.implementation;
		RelativeAddress nextAddress = new RelativeAddress(impl.getStackPointerRegister(), 0);
		// Addressing function's parameters first
		Iterator<String> piter = parameterNames.iterator();
		while (piter.hasNext()) {
			String name = piter.next();
			SymbolElement el = symbols.get(name);
			el.setStoreAddress(nextAddress);
			//nextAddress = new RelativeAddress(impl.getStackPointerRegister(), nextAddress.getValue() + el.getType().getValueLength());
			nextAddress = new RelativeAddress(impl.getStackPointerRegister(), nextAddress.getValue() + el.getType().sizeOf() / 4);
		}
		// Addressing function's local variables
		Iterator<String> siter = symbols.keySet().iterator();
		while (siter.hasNext()) {
			SymbolElement el = symbols.get(siter.next());
			if (parameterNames.contains(el.getName()) || el.getType().isStatic())
				continue;
			el.setStoreAddress(nextAddress);
			//nextAddress = new RelativeAddress(impl.getStackPointerRegister(), nextAddress.getValue() + el.getStoredSize());
			nextAddress = new RelativeAddress(impl.getStackPointerRegister(), nextAddress.getValue() + el.getType().sizeOf() / 4);
		}
		// Retrieving all instructions
		if (name.equals("_start")) {
			compiledInstructions.add(impl.getMov(impl.getStackPointerRegister(), impl.getImmediate(linker.getGlobalDataLength())));
		}
		else {
			compiledInstructions.add(impl.getSt(impl.getReturnRegister(), new RelativeAddress(impl.getStackPointerRegister(), 0), 4));
			//compiledInstructions.add(impl.getAdd(impl.getStackPointerRegister(), impl.getStackPointerRegister(), impl.getImmediate(4), false));
			compiledInstructions.add(impl.getAdd(impl.getStackPointerRegister(), impl.getStackPointerRegister(), impl.getImmediate(1), false));
		}
		// Initializing local variables
		siter = symbols.keySet().iterator();
		while (siter.hasNext()) {
			SymbolElement el = symbols.get(siter.next());
			if (parameterNames.contains(el.getName()) || el.getType().isStatic())
				continue;
			if (el == null)
				System.err.println("ERROR: Unable to find local variable");
			if (el.getInitialValue() == null)
				continue;
			Argument res = impl.getFreeRegister();
			el.computeInitialization(res, compiledInstructions, this);
			impl.freeRegister(res);
		}
		// Computing instructions
		if (instructions != null) {
			Iterator<Instruction> iter = instructions.iterator();
			while (iter.hasNext()) {
				Argument res = impl.getFreeRegister();
				iter.next().computeInstruction(res, compiledInstructions, this);
				impl.freeRegister(res);
			}
		}
		if (!name.equals("_start")) {
			//compiledInstructions.add(impl.getSub(impl.getStackPointerRegister(), impl.getStackPointerRegister(), impl.getImmediate(4), false));
			compiledInstructions.add(impl.getSub(impl.getStackPointerRegister(), impl.getStackPointerRegister(), impl.getImmediate(1), false));
			compiledInstructions.add(impl.getLd(impl.getReturnRegister(), new RelativeAddress(impl.getStackPointerRegister(), 0), 4));
			compiledInstructions.add(impl.getJmp(impl.getReturnRegister()));
		}
	}
	
	public int getStackSize() {
		int size = 0;
		
		Iterator<SymbolElement> iter = this.symbols.values().iterator();
		while (iter.hasNext()) {
			SymbolElement el = iter.next();
			if (el.getType().isStatic())
				continue;
			//size += el.getStoredSize();
			size++;
		}
		return size;
	}

	public int getStoredSize() {
		int size = 0;
		
		Iterator<glemipsc.implementations.Instruction> iter = compiledInstructions.iterator();
		while (iter.hasNext()) {
			glemipsc.implementations.Instruction instr = iter.next();
			if (!instr.isNop())
				size += instr.getSize();
		}
		return size;
	}
	
	public void write(OutputStream out) {
		Implementation impl = GLEMipsC.compiler.getEnvironment().implementation;
		
		try {
			// Writing signature
			if (GLEMipsC.compiler.getEnvironment().asmFile) {
				int funcSize = 0;
				Iterator<String> namesIter = this.parameterNames.iterator();
				while (namesIter.hasNext()) {
					funcSize += this.symbols.get(namesIter.next()).getStoredSize();
				}
				String signature = "/************************************/\n";
				signature += "// Function " + this.name + "@" + funcSize + " at " + this.addr.getValue() + " (stack size: " + getStackSize() + ")\n";
				Iterator<SymbolElement> symbolsIter = this.symbols.values().iterator();
				while (symbolsIter.hasNext()) {
					SymbolElement symbol = symbolsIter.next();
					signature += "// ";
					if (this.parameterNames.contains(symbol.getName()))
						signature += "@";
					signature += symbol.getType().toString() + " " + symbol.getName() + " at " + symbol.getStoreAddress().toString() + "\n";
				}
				out.write(signature.getBytes());
			}
			// Writing instructions
			Iterator<glemipsc.implementations.Instruction> iter = compiledInstructions.iterator();
			while (iter.hasNext()) {
				glemipsc.implementations.Instruction instr = iter.next();
				if (instr.isNop())
					continue;
				if (GLEMipsC.compiler.getEnvironment().asmFile)
					out.write(instr.getAsmCode().getBytes());
				else
					out.write(instr.getOpCode().getBytes());
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public List<glemipsc.implementations.Instruction> getCompiledInstructions() {
		return compiledInstructions;
	}

}
